/*
 * @LogFactory.java	2008/07/28
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import it.wzm.lightweightlog.annotation.DumpingProcessorFactory;
import it.wzm.lightweightlog.annotation.OpeningProcessor;
import it.wzm.lightweightlog.annotation.DumpingProcessor;
import it.wzm.lightweightlog.annotation.OpeningProcessorFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Properties;
import java.util.regex.Pattern;
import java.util.Observer;


/**
 * <p>
 * LogFactory is the accessor for getting all instances of Queue, QueueDumper and Register.<br>
 * This is a Factory Pattern: only one instance of each Objects, and a Factory which initialize and serve all of them.<br>
 * The initialization is done when the method <I>LogFactory.newLogger()</I> or <I>LogFactory.newLogger(name)</I> is called.<br>
 * If no 'name' is passed then the LogFactory will try to guess the class name which has requested to log an entry, otherwise a new Log with specific 'name' is served.
 * If there is already a valid log then it is returned, otherwise the factory will initialize each elements according to the <I>dumper.properties</I> file.<br>
 * If no properties are found on the classpath, then the default property file is loaded and initialization is made with such properties.<br/>
 * The initialization steps:
 * <ul>
 *    <li>a property file, dump.properties is searched in the classpath then if not found the default one inside package is taken</li>
 *    <li>Acquire properties from file, custom or default</li>
 *    <li>Initialize the LogPool</li>
 *    <li>Initialize the Dumpers</li>
 *    <li>Initialize the Register</li>
 *    <li>Initialize the Queue</li>
 *    <li>Initialize and start QueueDumper (if necessary)</li>
 *    <li>Serve the log...</li>
 * </ul>
 * 
 * If any of this point fail to initialize, then a LightweightlogInitializationException is thrown
 * and an initialization with default parameter is done.
 * </p>
 * @author Leonardo Celati
 */
public final class LogFactory implements Constant  {
    
	/** The observer */
	private static Observer h;
    /** Status of LogFactory */
    private static Status status = Status.OFF;  // default when class is loaded
    /** LogPool holder */
    private static LogPool<? extends Log> lp;
    /** The Queue holder */
    private static Queue<? extends Entry> q;
    /** The Register holder */
    private static Register<? extends Dumper> r;
    /** The QueueDumper holder */
    private static QueueDumper qd;
    /** Dumper holder */
    private static ArrayList<Dumper> dumpers;
    /** Properties */
    private static Properties p;
    /** Our disabled log */
    private static Log dlog;
    /** Maximum application log level */
    private static Severity maxSeverity;
	private static boolean serialize;
    
    /**
     * Implementing singleton, we don't want anyone to create an instance of this class
     */
    private LogFactory() {
    }
      
    /**
     * This method will try to guess the class name and build a log with such name
     * @return a valid Log
     */
    public static Log newLogger() {
    	Throwable t = new Throwable();
    	return newLogger(t.getStackTrace()[t.getStackTrace().length - 1].getClassName() );
    }
    
    /**
     * This method will build a log with the given name
     * @param classname the name for this logger
     * @return a valid Log
     */
    public static synchronized Log newLogger(String classname) {
    	try {
    		if (status == Status.OFF)
    			init();
    			    			
    		// different behaviour upon status
    		switch (LogFactory.status) {
    		    			
    			case INIT:    				
    				// TO-DO wait till running...
    				
    				break;
    		
    			case RUNNING:
    				assert(lp!=null):"Constructor is null and status is running, that's not possible";
    				// returning log
    				//Log l =  (Log) lcst.newInstance(new Object[] {name,p});
    				return lp.getInstance(classname);

    			case FAILURE:
    				// uhm...houston we have a problem
    				// let's return the safe log...
    				break;
    				
    			case DISABLED:
    				
    				return dlog;
    				
    			default:
    				// we are doing nothing in all other unrecognized case
    				// Shutdown is a recognized case but we are not taking
    				// any action, therefore it goes in default...
    				break;
    		
    		}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
    }
    
    /**
     * Getter for status
     * @return Status
     */
    public static final Status getStatus() {
    	return status;
    }
           
    /**
     * Get the created Dumpers
     * @return the dumpers
     */
    public static final ArrayList<Dumper> getDumpers() {
        return dumpers;
    }
    
    /**
     * Private method for init our Log
     * @param is The InputStream of our properties file
     */
    private static void init() {
    	
    	// we can only init if we are in OFF state...
    	if (status!=Status.OFF) 
    		return;

    	// registering our hook....
    	Runtime.getRuntime().addShutdownHook(new ShutdownHook());
    	// registering event handler
        initObserverHandler();

    	// try to find the property file, if not found the default one is taken
    	InputStream is=null;	
        try {
        	if ((is = LogFactory.class.getClassLoader().getResourceAsStream(PROP_NAME)) == null) {
        		is = LogFactory.class.getClassLoader().getResourceAsStream(PROP_DEFAULT);
        	}

            // load property file
            Properties fp = new Properties();
            try {
            	fp.load(is);
            } catch(IOException e) {
            	throw new LightweightlogInitializationException(e.getCause());
            }
            
            // if we are in disabled mode...
            // we just build a Log object and set
            // its status to DISABLED            
            if (fp.get("dumper.logfactory.status") !=null && fp.get("dumper.logfactory.status").equals("disabled")) {
            	status = Status.DISABLED;
            	//dlog = new Log();
            	//dlog.setStatus(Status.DISABLED);
            	return;
            }

        	// set mode...
        	status = Status.INIT;
        	
            // then we init in turn all our objects...
            initPool(fp);
            initDumper(fp);
            initRegister(fp, getDumpers());
            initQueue(fp);
            // we check if our Queue is "Not Cumulable"
            // which means we drop our entry directly to Queue
            // and we do not need to start QueueDumper....
            // otherwise we start it in daemon mode
            if (q.isCumulable()) {
                initQueueDumper(fp);
                // we add an observer to QueueDumper because
                // LogFactory is not tight and does not know about
                // QueueDumper...so when it fails LogFactory knows it
                qd.addObserver(h); 
                Thread t = new Thread(getQueueDumper());
                t.setName("QueueDumper");
                t.setDaemon(true);
                t.start();
            }
            // set status
            status = Status.RUNNING;
            
        } catch (LightweightlogInitializationException e) {
        	// sending message to user...
        	System.out.println("Couldn't start lightweightlog facility\r\n");
        	e.printStackTrace();
        	
            // Log didn't initialize properly...
            // so we clean any existing object
        	if (getDumpers() !=null) {
        		for (Dumper x: getDumpers())
        			if (x != null) x.kill();
        	}
        	
        	if(lp!=null) {
        		lp.kill();
        		lp=null;
        	}
            
            if (r !=null) {
                r.kill();
                r=null;
            }
            
            if (qd !=null) {
                qd.kill();
                qd=null;
            }
        }
    }
        
    /**
     * Initialize all our dumper
     * @param fp Custom set of properties
     * @throws LightweightlogInitializationException if at least a Dumper with <I>killonfailure</I> fail to initialize
     */
    private static void initDumper(Properties fp) throws LightweightlogInitializationException {
    	
    	// basic check...
    	if (getDumpers() !=null) {
    		//l.warn("Cannot instantiate twice");
    		return;
    	}
        
        try {
            // start with looking for dumper.name=.....,....,.....
            String[] declaredDumpers = Pattern.compile(",").split(fp.getProperty(Constant.PROP_ROOT + "name"));
            // nothing to say...if dumper are found
            // we store them into register
            // if any error occur initializing the dumper we run in emergency mode
            if (declaredDumpers.length > 0) {
                // uhm...some dumper found, let's have a look
                // we need to store them on register
                dumpers =  new ArrayList<Dumper>(declaredDumpers.length);
                
                // looping trough them...
                for (String name : declaredDumpers) {
                    // dumper holder...
                    Dumper dumper = null;
                    try {
                        // classname to look for...
                        String classname = fp.getProperty(PROP_ROOT + name + ".classname");

                        // load class and its corresponding constructor...
                        Class<?> c = Class.forName(classname.trim());

                        // use reflection for the rest...all successful dumper goes to array
                        Constructor<?> cst = c.getConstructor(String.class, Properties.class);
                        dumper = (Dumper) cst.newInstance(new Object[]{name,fp});

                        // introspecting our dumper to see if annotations are present
                        // and if they are active or not
                        if (dumper.getClass().isAnnotationPresent(DumpingProcessor.class)) {                    		
                    		DumpingProcessor dp = dumper.getClass().getAnnotation(DumpingProcessor.class);
                    		if (dp.active())
                    			dumper = new DumpingProcessorFactory(dumper);
                        }

                        if (dumper.getClass().isAnnotationPresent(OpeningProcessor.class)) {
                    		// see if it's active
                    		OpeningProcessor op = dumper.getClass().getAnnotation(OpeningProcessor.class);
                    		if (op.active())
                    				dumper = new OpeningProcessorFactory(dumper);
                        }
                        
                        // store and backup dumper...
                        dumpers.add(dumper);
                        //LogUtil.backup(dumper);

                    } catch (RuntimeException e) {
                            throw new LightweightlogInitializationException(e.getCause());
                    } catch (Exception e) {
                            throw new LightweightlogInitializationException(e.getCause());
                    }
                }
            }
            
        } catch(Exception e) {
            throw new LightweightlogInitializationException(e);
        }
    }
         
    /**
     * Initialize our LogPool, if there is already a valid instance this method will just 'return'
     * @param p custom set of properties
     * @throws LightweightlogInitializationException if log fail to initialize
     */
    private static void initPool(Properties p) throws LightweightlogInitializationException {
        // we first check if we already have a
        // LogPool available, if not we instantiate
        // and store it in variable 'lp'
    	if (lp !=null) {
    		return;
    	} else {
            try {
            	// search for classname in property file...
                String classname = p.getProperty(Constant.PROP_ROOT + "logpool.classname", Constant.DEFAULT_POOL);
                // then...all that follows to get a valid Register with reflection...
                Class<?> c = Class.forName(classname.trim());
                Constructor<?> cst = c.getConstructor(Properties.class);
                lp = (LogPool<? extends Log>) cst.newInstance(new Object[] {p});
                
                
                //LogUtil.backup(lp);
                                
            } catch (RuntimeException e) {
                throw new LightweightlogInitializationException(e);
            } catch (Exception e) {
                throw new LightweightlogInitializationException(e);
            }
        }
    }
    
    
    /**
     * Initialize our Queue, if there is already a valid instance this method will just 'return'
     * @param p custom set of properties
     * @throws it.wzm.lightweightlog.QueueInitializationException if queue fail to initialize
     */
    @SuppressWarnings("unchecked")
    private static void initQueue(Properties p) throws LightweightlogInitializationException {
        
        // we first check if we already have a
        // queue available, if not we instantiate
        // and store it in variable 'q'
    	// basic check...
    	if (q !=null) {
    		return;
    	} else {
            try {
                // search for classname in property file...
                String classname = p.getProperty(Constant.PROP_ROOT + "queue.classname", Constant.DEFAULT_QUEUE);
                // then...all that follows to get a valid Register with reflection... 
               
                // deciding which instance of Queue we want to create
                Class<?> c = Class.forName(classname.trim());
                Constructor<?> cst = c.getConstructor(Properties.class);
                q = (Queue<? extends Entry>) cst.newInstance(new Object[] {p});
                
                // backup...
                LogUtil.backup(q);
                
            } catch (RuntimeException e) {
                throw new LightweightlogInitializationException(e);
            } catch (Exception e) {
                throw new LightweightlogInitializationException(e);
            }
        }
    }
    
    /**
     * Return current queue size
     * @return int -1 if null otherwise the actual Queue size
     */
    public static int getQueueSize() {
        // -1 means null...
        if (q!=null)
            return q.size();
        else
            return -1;
    }
    
    /**
     * Return status of Queue
     */
    public static Queue.Status getQueueStatus() {
    	if (q!=null)
    		return q.getStatus();
    	else
    		return Status.OFF;
    }
    
    /**
     * Initialize our register, if there is already a valid instance this method will just 'return'
     * @param p custom set of properties
     * @param d Our array of Dumper
     * @throws it.wzm.lightweightlog.LightweightlogInitializationException if register fail to initialize
     */
    @SuppressWarnings("unchecked")
    private static void initRegister(Properties p, ArrayList<? extends Dumper> d) throws LightweightlogInitializationException {
        // check if we already have it...
    	if (getRegister() !=null) {
    		return;
    	} else {
            // search for classname in property file...
            String classname = p.getProperty(Constant.PROP_ROOT + "register.classname", Constant.DEFAULT_REGISTER);
            
            try {
            	// then...all that follows to get a valid Register with reflection...
                Class<?> c = Class.forName(classname.trim());
                Constructor<?> cst = c.getConstructor(Properties.class,ArrayList.class);
                // store register...
                r = (Register<? extends Dumper>) cst.newInstance(new Object[] {p,d});     
                // backup...
                //LogUtil.backup(r);

            } catch (RuntimeException e) {e.printStackTrace();
            	throw new LightweightlogInitializationException(e);
            } catch (Exception e) {e.printStackTrace();
            	throw new LightweightlogInitializationException(e);
            }
        }
    }
    
    /**
     * Getter for register size
     * @return int -1 if null otherwise the actual Register size
     */
    public static int getRegisterSize() {
        // -1 means null
        if (r!=null)
            return r.size();
        else
            return -1;
    } 
    
    /**
     * Initialize our QueueDumper, if there is already a valid instance this method will just 'return'
     * @param p custom set of properties
     * @throws it.wzm.lightweightlog.QueueLightweightlogInitializationException if fail to initialize
     */
    private static void initQueueDumper(Properties p) throws LightweightlogInitializationException {
        
        // we first check if we already have a
        // queue dumper available, if not we instantiate
        // and store it in variable 'q'
    	if (getQueueDumper() !=null) {
    		//l.warn("Cannot instantiate twice");
    		return;
    	} else {
            try {
                // search for classname in property file...
                String classname = p.getProperty(PROP_ROOT + "queuedumper.classname", DEFAULT_QUEUE_DUMPER);

                // then...all that follows to get a valid Register with reflection...
                Class<?> c = Class.forName(classname.trim());
                Constructor<?> cst = c.getConstructor(Properties.class);
                qd = (QueueDumper) cst.newInstance(new Object[] {p});
                // adding the observer...
                qd.addObserver(h);                
            } catch (RuntimeException e) {
                throw new LightweightlogInitializationException(e);
            } catch (Exception e) {
                throw new LightweightlogInitializationException(e);
            }
        }
    }
    
    /**
     * Return the used LogPool
     * @return the current LogPool
     */
    static LogPool getLogPool() {
    	return lp;
    }
    
    /**
     * Return the LogPool size. In case pool it's not initialized the returned value is  -1
     * @return int
     */
    public static int getLogPoolSize() {
        // -1 means null...
        if (lp!=null)
            return lp.size();
        else
            return -1;
    }
    
    
    /**
     * Getter for Queue
     * @return Queue
     */
    @SuppressWarnings("unchecked")
	static Queue getQueue() {
        return q;
    }
    
    /**
     * Getter for Register
     * @return Register
     */
    static Register<? extends Dumper> getRegister() {
        return r;
    }
    
    /**
     * Getter for QueueDumper
     * @return QueueDumper
     */
    static QueueDumper getQueueDumper() {
        return qd;
    }
    
    
    /**
     * Initialize our Observer
     */
    private static void initObserverHandler() {
    
	    // our Handler...
	    final class LogObserverHandler implements Observer {
	    	
	    	LogObserverHandler() {
	    	}
	    	
	        /** Implementing method as requested by Observer */
	    	public void update(Observable o, Object arg) {
	    		// check what happened...
	    		if (o instanceof QueueDumper) {
	    			this.queueDumperHandler(arg);
	    			return;
	    		}        		
	    	}       	
	    	/** If QueueDumper fail...*/
	    	private void queueDumperHandler(Object arg) {
	    		// see what happened...
	    		if (arg instanceof QueueDumperException) {	    			
	    			LogFactory.initQueueDumper(p);
	    		} else {
	    			System.out.println(arg);
	    		}
	    	}
	    }
	    // initialize...
	    h =  new LogObserverHandler();
    }
    
}

